Lr scheduler

Last-modified: 2025-02-16 (日) 10:29:40

sd-scriptsにおけるLr scheduler

概要

  • 学習率を時系列変化させるLearning rate専用のスケジューラ。
    学習のstep進行に応じて、学習率Learning rateへ0~1の値を掛けて学習率を調整する。1を基準として低下させていく。
    スケジューラによって、Lrの低下具合が異なる。

目的

  • Lr schedulerの一番の目的は、過学習しないようにLrに徐々にブレーキを掛けること。
    • 「学習したいものを絞り出す能力」ともいえる。
      • 通常、適度に大きなLrは構図やポーズを学習するのには便利だが、Lrスケジューラで調整しない限り、土台である構図が安定せず、なかなかディティール学習が進まない。ディティールが安定しないと、構図の学習も疎かになるという悪循環に陥りがちである
      • 1stepで1つのLrしか適用できない学習の仕様上、高いLrで学習→クール期間を挟みつつ小さいLrで学習する方法が最も安定する*1
    • Lrを落とす結果、内部的には学習量は落ちるが、ウェイトが増えたり減ったりの無駄な計算が減るため、高めのLrと組み合わあわせて適切に設定することで結果として学習時間の短縮に繋がる。

使用方法

  • 使用するには、次の行を編集することでオプション指定を行います。
       --lr_scheduler=
  • 選択したlr_schedulerによっては追加のオプションが必要な場合があります。
  • オプティマイザーがAdaFactorかつ、relative_step=trueの場合、Lr schedulerは自動的にadafactorが選択される。
  • sd-scriptsのプリセット以外にカスタムスケジューラも使用可能。

Lr Schedulerの種類

sd-scriptsのプリセットは下表の通り。

名称ソース
--lr_scheduler=特徴追加の必要オプション*2HP*4背景技術備考
linear初期値をlr指定値、最終Epoch時をlr=0として線形的に減少する
cosine設定したlrをピークとしてcosineカーブで最終epochで0となるよう減衰
cosine_with_restartscosine関数状の学習率変化を、全Epoch内で指定回数繰り返す。--lr_scheduler_num_cycles=(整数)--lr_scheduler_num_cycles=1で繰り返し無し=kohya_ss guiデフォルト値
polynomial多項式曲線上にlrが変化する。--lr_scheduler_power=11を基準(linearと同一挙動)とし、値が大きいほどlrの減衰大、値が小さいほど減衰小
constantlr一定
=1
constant_with_warmup指定ステップ数lr_warmup_stepsまで直線的に増加。以降はconstantで動作--lr_warmup_steps=(整数)
adafactoradafactorオプティマイザー専用のスケジューラ。コード内部に存在するスケジューラで、ユーザーが選択する必要はない。adafactorオプティマイザー選択時に自動で使われる。train_util.pyに存在する
Lr_scheduler_image_B.jpg

:constant、:linear、:cosine
※イメージ図なので、実際の値とは多少差異有り。

Lr scheduler選定の留意点

基本的にcosine_with_restartsで多くのニーズを満たせるはずだが、
オプティマイザーやEpoch数(全step中に教師画像が何回・どんな分布で登場するか)の相性の都合で、他のLr scehdulerを選択する場合がある。

基本的な考え方

一概に答えはありませんが、下記の傾向はあります。

  • Lrの時間積分値が大きいほど、全体の学習量が増える。→Epoch数を減らせる(時短)。
  • 自動型よりも手動型オプティマイザー(Lr固定)の方が、スケジューラ選定が比較的重要。なぜなら、下記要件を同時に満たすには、スケジューラ以外に頼れないからです。
    • Epoch前半:Lr倍率が大きい方が良い。学習量が増え、局所最適解に留まらずに済む
      (目標とは程遠いのになかなか学習が進まない状態のこと。谷を越えるとも言う)
    • Epoch後半:Lr倍率は適度に小さい方が良い。過学習せずにディティールを学習できる。

オプティマイザーとの相性

オプティマイザーによっては、その専用のパラメータが前stepの状態を参照している場合があります。
Lionが該当。不安定なスケジューラは不安定なウェイト更新をもたらし、間接的にオプティマイザーの状態に影響を与えるでしょう。

lr_warmup_steps併用も考慮する。

  • 学習初期のstepではオプティマイザーの状態が安定していないため、warmupにより徐々に学習率を倍率1まで上げる方法がある。
    --lr_warmup_steps=10
    #この場合は0~10stepの間、0からLrの設定値まで直線的に増加する。
  • 学習開始直後に不安定になる場合に適用すると良い。
    • もし目安が欲しいなら、lossを監視して急激なloss変動が落ち着くあたりのstep数を指定すれば良いと思う。

Tips

cosine系の欠点と対策

  • cosineの欠点
    cosine系は、後半のstep全体のかなり長いstep区間で学習率がほぼ0になる。
    そのため、教師画像の何割かが無駄になるおそれがある
    • たとえば、1epoch(かつrestart数=1)にしてしまうと、まったく学習されない画像が出てくる。
    • 特に、有効なLearning rateのバンドが狭いオプティマイザーを使用するほど、ほとんどの学習が無駄か、特定の教師画像に偏った学習になる。
    • 自動型オプティマイザーの場合、cosineのせいでLrが微妙なところで安定してしまうため。cosineとは相性が悪い(本来高性能なprodigyが使いにくい一因である。)
  • 対策
    Lr schedulerを改造して底上げを図ると良い。
    • cosine_with_restartsの場合の改造例は下記の通り。
      • optimization.py*5を参照し、下記のdef lr_lambda部分を修正。
        min_value=0.5として、最小値が0.5までしか下がらないようにする。min_valueの値は0.3~0.6程度の範囲で調整すると良い。
        コード改造例

        ※わからないことがあれば、ChatGPTに聞けば答えてくれるはず。

        
        def get_cosine_with_hard_restarts_schedule_with_warmup(
            optimizer: Optimizer, num_warmup_steps: int, num_training_steps: int, num_cycles: int = 1, last_epoch: int = -1
        ) -> LambdaLR:
            def lr_lambda(current_step):
                if current_step < num_warmup_steps:
                    return float(current_step) / float(max(1, num_warmup_steps))
                progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))
                if progress >= 1.0:
                    return 0.0
        
                # Learning rateの最小値と最大値を設定する。
                max_value=1.0
                min_value=0.5
        
                # 既存の数式を、min_valueを考慮した数式に変換する。
                return max(0.0, min_value + (max_value - min_value) * (0.5 * (1.0 + math.cos(math.pi * ((float(num_cycles) * progress) % 1.0)))))
            return LambdaLR(optimizer, lr_lambda, last_epoch)
  • 対策後の状態

    Lr_scheduler係数の値。最低値が0.5まで増加した。その結果、学習がみるみる進む。
    が変更前、が変更後。横軸はstep数
    Cosine_with_restarts_改造結果_Lr_scheduler係数.jpg

    下図はProdigyによる補正後Lr*6。上図のグラフと同一のデータ。
    cosineの影響で学習率が十分に大きくならないまま安定し、再びLr scheduler=1に戻っても低い学習率を維持している。
    Cosine_with_restarts_改造結果_Prodigyの補正後Lr.jpg

    • 今回の検証では、restart間隔がProdigyのLr安定よりも長いため、低い学習率で安定したようだが、それ以外の状況だとどうなるかわからない。いずれにせよ、Lr schedulerを底上げした方が、schedulerの支配を受けにくい安定した結果が得られるだろう。

その他

使用者のコメントです。

もっと読む
  • 一部の手動オプティマイザーでは、constantなら白背景などの手間がかかり、さもなくば出来上がった絵の色んな所にあるべきではないものが時々見かけられます。
    consine_with_restartなら、特に背景を気をする必要はありません。そのままで学習できます。
  • cosine_with_restartなら1エポックの総学習値がconstantより低いので、もっとエポック数を回すか、学習率をより大きく設定する必要があるかも。
    • つまり1エポックのcosine_with_restartの面積を積分して、1エポックのconstant(長方形)に比例した値の逆数が、その倍数です。
      公式を忘れたので、具体的な値はできる方が計算してここに記してください。
      たぶんπ/2≈1.57の気がします。間違ったらすみません。
      2.04ぐらいの可能性もありそうです。計算ができる方はあとから修正お願いします。
  • warm_upは10%を使ってみたが、特にこれといった効果はありませんでした。
    • 初期段階の収斂速度がすこし遅くなるだけで、特に完成モデルに重大な影響を出していない。可もなく不可もなし。少なくとも画像学習分野では特に役がたたないかも。
  • warm_up不要説
    • オンにするとスタート後しばらく、ほぼ結果が変わらない学習に時間を使う。やってる事はスタート地点の微調整であり、違う学習モデルを使う事に比べれば誤差は皆無。
      そもそも初期段階でいきなり過学習になる時点で初期設定に問題がある気がする。
  • cosine_with_restartで、restart数は1よりも2のほうが好みです。
    • なぜなら、cosine_with_restart=1だと、
      学習末期ではLrが小さくなり教師画像全ての平均的な最適解で収束します。
      結果として、シード値によっては別人のキャラが生成されます。
      ディテールは良くなるのに、全体の雰囲気が似てない感じです。
    • 一方、restart=2にすると、学習中期にてText Encoderの学習が進み対応力がついた頃に、再び大きなLrを適用するため、画像全体に対して概念を再考する、さらに教師画像を絞り出す学習が行われます。
    • ただし、再び収束させるための期間稼ぎとして、Epoch数をやや増やす必要が出てきます。
    • あくまでも、cosine_with_restartを使うならの話で、それ以外の最適解もあり得るでしょう。
  • cosineやlinearは少epochでは何も学ばないレベルで設定値からどんどん学習率が下がっていくので学習をやり始めの頃は変化が出やすいconstantを推奨

*1 SDXL後継モデルがよほど覚えの良い構造でない限り。また、革命的なディティール学習用オプションが登場しない限り(方向性としては、オプティマイザーやmin_SNR_gammaなどが相当する)。Lr調整による構図調整能力が大きいため、現状もっとも有効な手段である。
*2 不要なら「ー」、空欄=情報なし
*3 diffusersではなくtransofomersライブラリ同梱のLr Schedulerを使用している等
*4 sd-scriptsのVerによって異なる場合があるが*3、基本的な機能や設定は同じ
*5 保存場所はsd-scripts/library/train.utilのVerによって異なる。およそ'24/9/13以前はdiffusers、それ以降はtransofomersフォルダに保存されているものを参照している
*6 =Prodigy補正係数d✕Lr_scheduler✕基礎Lr